import type { UseDrawerReturnType, DrawerInstance, ReturnMethods, DrawerProps, UseDrawerInnerReturnType } from "./typing"
import { ref, onUnmounted, unref, getCurrentInstance, reactive, watchEffect, nextTick, toRaw, computed } from "vue"
import { isProdMode } from "/@/utils/env"
import { isFunction } from "/@/utils/is"
import { isEqual } from "lodash-es"
import { tryOnUnmounted } from "@vueuse/core"
import { error } from "/@/utils/log"

const dataTransferRef = reactive<any>({})

const openData = reactive<{ [key: number]: boolean }>({})

/**
 * @description: Applicable to separate drawer and call outside
 */
export function useDrawer(): UseDrawerReturnType {
  const drawer = ref<DrawerInstance | null>(null)
  const loaded = ref<Nullable<boolean>>(false)
  const uid = ref<string>("")

  function register(drawerInstance: DrawerInstance, uuid: string) {
    if (!getCurrentInstance()) {
      throw new Error("useDrawer() can only be used inside setup() or functional components!")
    }
    uid.value = uuid
    isProdMode() &&
      onUnmounted(() => {
        drawer.value = null
        loaded.value = false
        dataTransferRef[unref(uid)] = null
      })
    if (unref(loaded) && isProdMode() && drawerInstance === unref(drawer)) return

    drawer.value = drawerInstance
    loaded.value = true

    drawerInstance.emitOpen = (open: boolean, uid: number) => {
      openData[uid] = open
    }
  }

  const getInstance = () => {
    const instance = unref(drawer)
    if (!instance) {
      error("useDrawer instance is undefined!")
    }
    return instance
  }

  const methods: ReturnMethods = {
    setDrawerProps: (props: Partial<DrawerProps>): void => {
      getInstance()?.setDrawerProps(props)
    },

    getOpen: computed((): boolean => {
      return openData[~~unref(uid)]
    }),

    openDrawer: <T = any>(open = true, data?: T, openOnSet = true): void => {
      getInstance()?.setDrawerProps({
        open: open,
      })

      if (!data) return
      const id = unref(uid)
      if (openOnSet) {
        dataTransferRef[id] = null
        dataTransferRef[id] = toRaw(data)
        return
      }
      const equal = isEqual(toRaw(dataTransferRef[id]), toRaw(data))
      if (!equal) {
        dataTransferRef[id] = toRaw(data)
      }
    },

    closeDrawer: () => {
      getInstance()?.setDrawerProps({ open: false })
    },

    setDrawerData: (data: any) => {
      if (!data) return
      nextTick(() => {
        setTimeout(() => {
          const id = unref(uid)
          dataTransferRef[id] = null
          dataTransferRef[id] = toRaw(data)
          return
        }, 100)
      })
    },
  }

  return [register, methods]
}

export const useDrawerInner = (callbackFn?: Fn): UseDrawerInnerReturnType => {
  const drawerInstanceRef = ref<Nullable<DrawerInstance>>(null)
  const currentInstance = getCurrentInstance()
  const uidRef = ref<string>("")

  const getInstance = () => {
    const instance = unref(drawerInstanceRef)
    if (!instance) {
      error("useDrawerInner instance is undefined!")
      return
    }
    return instance
  }

  const register = (modalInstance: DrawerInstance, uuid: string) => {
    isProdMode() &&
      tryOnUnmounted(() => {
        drawerInstanceRef.value = null
      })

    uidRef.value = uuid
    drawerInstanceRef.value = modalInstance
    currentInstance?.emit("register", modalInstance, uuid)
  }

  watchEffect(() => {
    const data = dataTransferRef[unref(uidRef)]
    if (!data) return
    if (!callbackFn || !isFunction(callbackFn)) return
    nextTick(() => {
      callbackFn(data)
    })
  })

  return [
    register,
    {
      changeLoading: (loading = true) => {
        getInstance()?.setDrawerProps({ loading })
      },

      changeOkLoading: (loading = true) => {
        getInstance()?.setDrawerProps({ confirmLoading: loading })
      },
      getOpen: computed((): boolean => {
        return openData[~~unref(uidRef)]
      }),

      closeDrawer: () => {
        getInstance()?.setDrawerProps({ open: false })
      },

      setDrawerProps: (props: Partial<DrawerProps>) => {
        getInstance()?.setDrawerProps(props)
      },
    },
  ]
}
